/*******************************************************************************
* Copyright (c) 2008, Jeferson Jose de Miranda                                 *
* UDESC - Universidade do Estado de Santa Catarina                             *
* All rights reserved.                                                         *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*     * Redistributions of source code must retain the above copyright         *
*       notice, this list of conditions and the following disclaimer.          *
*     * Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*     * Neither the name of the Universidade do Estado de Santa Catarina nor   *
*       the names of its contributors may be used to endorse or promote        *
*       products derived from this software without specific prior written     *
*       permission.                                                            *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY JEFERSON JOSE DE MIRANDA "AS IS" AND ANY        *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE       *
* DISCLAIMED. IN NO EVENT SHALL JEFERSON JOSE DE MIRANDA BE LIABLE FOR ANY     *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES   *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; *
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF     *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            *
*******************************************************************************/

/**
 * \file FrameFeatures.hpp
 *
 * \brief Extracts features from gray level images.
 *
 * \author Jeferson Jose de Miranda
 */

#ifndef FRAMEFEATURES_HPP
#define FRAMEFEATURES_HPP

#include "GreyLevelImage.hpp"
#include "ExtremePoints.hpp"
#include "Point2DMath.hpp"
// Move-In ROI management.
#include "ROIGroup.hpp"

namespace MoveIn
{
    /**
    * \class FrameFeatures
    *
    * \brief This is one of the most important classes in the Move-In API.
    * Its purpose is to extract features from binary images.
    */
    class FrameFeatures
    {
        public:

        /**
        * \fn FrameFeatures::differenceRatio(GreyLevelImage*,
        *                                    GreyLevelImage*)
        *
        * \brief Calculates the difference ratio between two binary images.
        *
        * \param image1
        * One of the binary images.
        *
        * \param image2
        * The other binary image.
        *
        * \return A floting point nunmber representing the difference ratio
        * between the provided images.
        */
        static float differenceRatio(GreyLevelImage *image1,
                                     GreyLevelImage *image2);

        /**
        * \fn FrameFeatures::activeArea(GreyLevelImage*,
        *                               CvRect*)
        *
        * \brief Calculates ratio of white area in a binary image.
        *
        * \param source
        * The source image.
        *
        * \return A floting point nunmber representing the ratio of the
        * affected area in the source image.
        */
        static float activeArea(GreyLevelImage *source,
                                CvRect *ROI = NULL);

        /**
        * \fn FrameFeatures::averageIntensity(RGBImage*)
        *
        * \brief Calculates the average pixel intensity level. Values can vary
        * from 0 to 255.
        *
        * \param source
        * The source image.
        *
        * \return An integer value indicating the average intensity.
        */
        static unsigned short int averageIntensity(RGBImage *source);

        static void convexHull(GreyLevelImage *source,
                               PointSequence *convex_hull,
                               PointSequence *contours=NULL);

        static void extremePoints(IplImage *source,
                                  ExtremePoints *extreme_points);

        static void extremePoints(GreyLevelImage *source,
                                  ExtremePoints *extreme_points,
                                  PointSequence *contours = NULL);

        static void extremePoints(GreyLevelImage *source,
                                  PointSequence *extreme_points,
                                  unsigned int distance,
                                  float angle,
                                  PointSequence *contours = NULL);

        static void extremePoints(PointSequence *contours,
                                  PointSequence *extreme_points,
                                  unsigned int distance,
                                  float angle);

        static void contours(GreyLevelImage *source,
                             PointSequence *contours);

};

/*******************************************************************************
* MoveIn::FrameFeatures::differenceRatio                                       *
*******************************************************************************/

    float FrameFeatures::differenceRatio(GreyLevelImage *image1,
                                         GreyLevelImage *image2)
    {
        if(!Image::isSameSize(image1, image2))
        {
            throw new DifferentSize();
        }
        else
        {
            unsigned short int height = image1->getHeight();
            unsigned short int width = image1->getWidth();
            unsigned short int color1, color2;
            float counter = 0;

            for(int x=0; x<width; x++)
            {
                for(int y=0; y<height; y++)
                {
                    color1 = image1->getPixel(x, y);
                    color2 = image2->getPixel(x, y);
                    if(color1 != color2)
                        counter++;
                }
            }
            return (float) counter/(height*width);
        }
        return 0.0f;
    }

/*******************************************************************************
* MoveIn::FrameFeatures::activeArea                                            *
*******************************************************************************/

    float FrameFeatures::activeArea(GreyLevelImage *source,
                                    CvRect *ROI)
    {
        unsigned short int color = 0;
        float counter = 0;

        if(ROI == NULL)
        {
            for(int x=0; x<source->getWidth(); x++)
            {
                for(int y=0; y<source->getHeight(); y++)
                {
                    color = source->getPixel(x, y);
                    if(color != 0)
                    {
                        counter++;
                    }
                }
            }
            return (float) counter/(source->getWidth()*source->getHeight());
        }
        else
        {
            if(ROIGroup::position(source, *ROI) != INSIDE)
            {
                return 0.0;
            }
            for(int x=ROI->x; x<(ROI->x+ROI->width); x++)
            {
                for(int y=ROI->y; y<(ROI->y+ROI->height); y++)
                {
                    color = source->getPixel(x, y);
                    if(color != 0)
                    {
                        counter++;
                    }
                }
            }
            return (float) counter/(ROI->width*ROI->height);
        }
    }

/*******************************************************************************
* MoveIn::FrameFeatures::convexHull                                            *
*******************************************************************************/

    void FrameFeatures::convexHull(GreyLevelImage *source,
                                   PointSequence *convex_hull,
                                   PointSequence *contours)
    {
        CvMemStorage *storage;
        CvSeq *sequence=NULL;
        CvPoint *convex_array;
        int *hull, hull_size;

        storage = cvCreateMemStorage(0);

        cvFindContours(source->getIplImage(),
                       storage,
                       &sequence,
                       sizeof(CvContour),
                       CV_RETR_EXTERNAL,
                       CV_CHAIN_APPROX_NONE);

        if(sequence == NULL)
        {
            cvReleaseMemStorage(&storage);
            return;
        }

        convex_array = (CvPoint*) malloc( sequence->total*sizeof(CvPoint) );
        hull = (int*) malloc(sizeof(int)*sequence->total);
        cvCvtSeqToArray(sequence, convex_array, CV_WHOLE_SEQ);
        if(contours != NULL)
        {
            for(int i=0; i<sequence->total; i++)
            {
                contours->append(convex_array[i]);
            }
        }

        cvConvexHull(convex_array,
                     sequence->total,
                     NULL,
                     CV_COUNTER_CLOCKWISE,
                     hull,
                     &hull_size);

        for(int i=0; i<hull_size; i++)
        {
            convex_hull->append(convex_array[hull[i]]);
        }

        cvClearSeq(sequence);
        cvReleaseMemStorage(&storage);
        free(convex_array);
        free(hull);
    }

/*******************************************************************************
* MoveIn::FrameFeatures::extremePoints                                         *
*******************************************************************************/

    void FrameFeatures::extremePoints(IplImage *source,
                                      ExtremePoints *extreme_points)
    {
        // Temporary memory storages.
        CvMemStorage *storage, *hull_storage, *defect_storage;
        // Temporary OpenCV sequences.
        CvSeq *sequence=NULL, *hull_sequence=NULL, *defect_sequence=NULL;
        // Temporary array of convex points;
        CvPoint *convex_array;
        // Temporary array of concave points.
        CvConvexityDefect *concave_array;
        // Hull indices and size.
        int *hull, hull_size;
        // Set memory storage for the sequence.
        storage = cvCreateMemStorage(0);
        // Find outer contours.
        cvFindContours(source,
                       storage,
                       &sequence,
                       sizeof(CvContour),
                       CV_RETR_EXTERNAL,
                       CV_CHAIN_APPROX_NONE);
        // In case of a zero valued source image, no contours will be found,
        // resulting in a null sequence.
        if(sequence == NULL)
        {
            cvReleaseMemStorage(&storage);
            return;
        }
        // Allocate memory for contour point set.
        convex_array = (CvPoint*) malloc( sequence->total*sizeof(CvPoint) );
        // Allocate memory for indices of convex hull vertices.
        hull = (int*) malloc(sizeof(int)*sequence->total);
        // Get contour point set from OpenCV sequence structure.
        cvCvtSeqToArray(sequence, convex_array, CV_WHOLE_SEQ);
        // Find convex hull for current contour.
        cvConvexHull(convex_array,
                     sequence->total,
                     NULL,
                     CV_COUNTER_CLOCKWISE,
                     hull,
                     &hull_size);
        // Insert the convex points on the resulting extreme points list.
        for(int i=0; i<hull_size; i++)
        {
            extreme_points->append(convex_array[hull[i]], CONVEX);
        }
        // Set memory storage for the sequences.
        defect_storage = cvCreateMemStorage(0);
        hull_storage = cvCreateMemStorage(0);
        // Find convex hull for contour.
        hull_sequence = cvContourConvexHull(sequence,
                                            CV_COUNTER_CLOCKWISE,
                                            hull_storage);
        // This required for cvConvexityDefects().
        // Otherwise cvConvexityDefects() falled.
        if(hull_size < 4)
        {
            cvClearSeq(sequence);
            cvReleaseMemStorage(&storage);
            cvClearSeq(hull_sequence);
            cvReleaseMemStorage(&hull_storage);
            free(convex_array);
            free(hull);
            return;
        }
        // Find defects of convexity of contour.
        defect_sequence = cvConvexityDefects(sequence,
                                             hull_sequence,
                                             defect_storage);
        // Allocate memory for temporary defect set.
        concave_array
        =
        (CvConvexityDefect*) malloc(sizeof(CvConvexityDefect)*defect_sequence->total);
        // Get defect set from sequence.
        cvCvtSeqToArray(defect_sequence, concave_array, CV_WHOLE_SEQ);
        // Insert the concave points on the resulting extreme points list.
        CvPoint tmp;
        for(int i=0; i<defect_sequence->total; i++)
        {
            for(unsigned int j=0; j<extreme_points->size(); j++)
            {
                tmp = *concave_array[i].start;
                if(extreme_points->equals(tmp, j))
                {
                    tmp = *concave_array[i].depth_point;
                    extreme_points->insert(tmp,
                                           CONCAVE,
                                           j);
                    break;
                }
            }
        }
        // Release all allocated temporary memory.
        cvClearSeq(sequence);
        cvReleaseMemStorage(&storage);
        cvClearSeq(hull_sequence);
        cvReleaseMemStorage(&hull_storage);
        cvClearSeq(defect_sequence);
        cvReleaseMemStorage(&defect_storage);
        free(convex_array);
        free(concave_array);
        free(hull);
    }

/*******************************************************************************
* MoveIn::FrameFeatures::extremePoints                                         *
*******************************************************************************/

    void FrameFeatures::extremePoints(GreyLevelImage *source,
                                      ExtremePoints *extreme_points,
                                      PointSequence *contours)
    {
        // Temporary memory storages.
        CvMemStorage *storage, *hull_storage, *defect_storage;
        // Temporary OpenCV sequences.
        CvSeq *sequence=NULL, *hull_sequence=NULL, *defect_sequence=NULL;
        // Temporary array of convex points;
        CvPoint *convex_array;
        // Temporary array of concave points.
        CvConvexityDefect *concave_array;
        // Hull indices and size.
        int *hull, hull_size;
        // Set memory storage for the sequence.
        storage = cvCreateMemStorage(0);
        // Find outer contours.
        cvFindContours(source->getIplImage(),
                       storage,
                       &sequence,
                       sizeof(CvContour),
                       CV_RETR_EXTERNAL,
                       CV_CHAIN_APPROX_NONE);
        // In case of a zero valued source image, no contours will be found,
        // resulting in a null sequence.
        if(sequence == NULL)
        {
            cvReleaseMemStorage(&storage);
            return;
        }
        // Allocate memory for contour point set.
        convex_array = (CvPoint*) malloc( sequence->total*sizeof(CvPoint) );
        // Allocate memory for indices of convex hull vertices.
        hull = (int*) malloc(sizeof(int)*sequence->total);
        // Get contour point set from OpenCV sequence structure.
        cvCvtSeqToArray(sequence, convex_array, CV_WHOLE_SEQ);
        if(contours != NULL)
        {
            for(int i=0; i<sequence->total; i++)
            {
                contours->append(convex_array[i]);
            }
        }
        // Find convex hull for current contour.
        cvConvexHull(convex_array,
                     sequence->total,
                     NULL,
                     CV_COUNTER_CLOCKWISE,
                     hull,
                     &hull_size);
        // Insert the convex points on the resulting extreme points list.
        for(int i=0; i<hull_size; i++)
        {
            extreme_points->append(convex_array[hull[i]], CONVEX);
        }
        // Set memory storage for the sequences.
        defect_storage = cvCreateMemStorage(0);
        hull_storage = cvCreateMemStorage(0);
        // Find convex hull for contour.
        hull_sequence = cvContourConvexHull(sequence,
                                            CV_COUNTER_CLOCKWISE,
                                            hull_storage);
        // This required for cvConvexityDefects().
        // Otherwise cvConvexityDefects() falled.
        if(hull_size < 4)
        {
            cvClearSeq(sequence);
            cvReleaseMemStorage(&storage);
            cvClearSeq(hull_sequence);
            cvReleaseMemStorage(&hull_storage);
            free(convex_array);
            free(hull);
            return;
        }
        // Find defects of convexity of contour.
        defect_sequence = cvConvexityDefects(sequence,
                                     hull_sequence,
                                     defect_storage);
        // Allocate memory for temporary defect set.
        concave_array
        =
        (CvConvexityDefect*)
        malloc(sizeof(CvConvexityDefect)*defect_sequence->total);
        // Get defect set from sequence.
        cvCvtSeqToArray(defect_sequence, concave_array, CV_WHOLE_SEQ);
        // Insert the concave points on the resulting extreme points list.
        CvPoint tmp;
        for(int i=0; i<defect_sequence->total; i++)
        {
            for(unsigned int j=0; j<extreme_points->size(); j++)
            {
                tmp = *concave_array[i].start;
                if(extreme_points->equals(tmp, j))
                {
                    tmp = *concave_array[i].depth_point;
                    extreme_points->insert(tmp,
                                           CONCAVE,
                                           j);
                    break;
                }
            }
        }
        // Release all allocated temporary memory.
        cvClearSeq(sequence);
        cvReleaseMemStorage(&storage);
        cvClearSeq(hull_sequence);
        cvReleaseMemStorage(&hull_storage);
        cvClearSeq(defect_sequence);
        cvReleaseMemStorage(&defect_storage);
        free(convex_array);
        free(concave_array);
        free(hull);
    }

/*******************************************************************************
* MoveIn::FrameFeatures::contours                                              *
*******************************************************************************/

    void FrameFeatures::contours(GreyLevelImage *source,
                                 PointSequence *contours)
    {
        // Temporary memory storage.
        CvMemStorage *storage;
        // Temporary OpenCV sequence.
        CvSeq *sequence=NULL;
        // Temporary array of contour points;
        CvPoint *contour_array;
        // Set memory storage for the sequence.
        storage = cvCreateMemStorage(0);
        // Find outer contours.
        cvFindContours(source->getIplImage(),
                       storage,
                       &sequence,
                       sizeof(CvContour),
                       CV_RETR_EXTERNAL,
                       CV_CHAIN_APPROX_NONE);
        // In case of a zero valued source image, no contours will be found,
        // resulting in a null sequence.
        if(sequence == NULL)
        {
            cvReleaseMemStorage(&storage);
            return;
        }
        // Allocate memory for contour point set.
        contour_array = (CvPoint*) malloc( sequence->total*sizeof(CvPoint) );
        // Get contour point set from OpenCV sequence structure.
        cvCvtSeqToArray(sequence, contour_array, CV_WHOLE_SEQ);
        // Convert contour array to Move-In point sequence.
        CvPoint tmp;
        for(int i=0; i<sequence->total; i++)
        {
            tmp = contour_array[i];
            contours->append(tmp);
        }
        // Release all allocated temporary memory.
        cvClearSeq(sequence);
        cvReleaseMemStorage(&storage);
        free(contour_array);
    }

/*******************************************************************************
* MoveIn::FrameFeatures::extremePoints                                         *
*******************************************************************************/

    void FrameFeatures::extremePoints(GreyLevelImage *source,
                                      PointSequence *extreme_points,
                                      unsigned int index_distance,
                                      float angle,
                                      PointSequence *contours)
    {
        // Temporary memory storage.
        CvMemStorage *storage;
        // Temporary OpenCV sequence.
        CvSeq *sequence=NULL;
        // Temporary array of contour points;
        CvPoint *contour_array;
        // Set memory storage for the sequence.
        storage = cvCreateMemStorage(0);
        // Find outer extreme_points.
        cvFindContours(source->getIplImage(),
                       storage,
                       &sequence,
                       sizeof(CvContour),
                       CV_RETR_EXTERNAL,
                       CV_CHAIN_APPROX_NONE);
        // In case of a zero valued source image, no extreme_points will be found,
        // resulting in a null sequence.
        if(sequence == NULL)
        {
            cvReleaseMemStorage(&storage);
            return;
        }
        // Allocate memory for contour point set.
        contour_array = (CvPoint*) malloc( sequence->total*sizeof(CvPoint) );
        // Get contour point set from OpenCV sequence structure.
        cvCvtSeqToArray(sequence, contour_array, CV_WHOLE_SEQ);
        // Convert contour array to Move-In point sequence.
        PointSequence temp_extreme_points;
        CvPoint temp_point;
        for(int i=0; i<sequence->total; i++)
        {
            temp_point = contour_array[i];
            temp_extreme_points.append(temp_point);
            if(contours != NULL)
            {
                contours->append(temp_point);
            }
        }
        CvPoint left_vector, right_vector, current_point;
        float alpha, dp;
        for(unsigned int i=0; i<temp_extreme_points.size(); i++)
        {
            // Get current point.
            current_point = temp_extreme_points.getPoint(i);
            // Get left and right neighbors.
            left_vector = temp_extreme_points.getPoint(i-index_distance);
            right_vector = temp_extreme_points.getPoint(i+index_distance);
            // Bring vectors to origin.
            left_vector.x -= current_point.x;
            left_vector.y -= current_point.y;
            right_vector.x -= current_point.x;
            right_vector.y -= current_point.y;
            // Calculate angle between vectors.
            dp = Point2DMath::dotProduct(left_vector,
                                         right_vector);
            alpha
            =
            Point2DMath::angle(dp);
            if(alpha >= angle && alpha <= angle+10)
            {
                extreme_points->append(current_point);
            }
        }
        // Release all allocated temporary memory.
        cvClearSeq(sequence);
        cvReleaseMemStorage(&storage);
        free(contour_array);
    }

/*******************************************************************************
* MoveIn::FrameFeatures::extremePoints                                         *
*******************************************************************************/

    void FrameFeatures::extremePoints(PointSequence *contours,
                                      PointSequence *extreme_points,
                                      unsigned int index_distance,
                                      float angle)
    {
        CvPoint left_vector, right_vector, current_point;
        float alpha, dp;
        for(unsigned int i=0; i<contours->size(); i++)
        {
            // Get current point.
            current_point = contours->getPoint(i);
            // Get left and right neighbors.
            left_vector = contours->getPoint(i-index_distance);
            right_vector = contours->getPoint(i+index_distance);
            // Bring vectors to origin.
            left_vector.x -= current_point.x;
            left_vector.y -= current_point.y;
            right_vector.x -= current_point.x;
            right_vector.y -= current_point.y;
            // Calculate angle between vectors.
            dp = Point2DMath::dotProduct(left_vector,
                                         right_vector);
            alpha
            =
            Point2DMath::angle(dp);
            if(alpha <= angle)
            {
                extreme_points->append(current_point);
            }
        }
    }

}

#endif


